namespace Microsoft.Robotics.Core.Algorithms
{
    using System;
    using System.Collections.Generic;
    using Microsoft.Robotics.Numerics;

    /// <summary>
    /// Basic sample accumulator. Computes rolling means, variance, standard deviation, holds sample total, count and the last value
    /// </summary>
    public sealed class Sampler
    {   
        /// <summary>
        /// Intermediary holder for rolling variance
        /// </summary>
        private double preVariance;

        /// <summary>
        /// Initializes a new instance of the <see cref="Sampler" /> class.
        /// </summary>
        public Sampler() 
        {
            this.Reset();
        }

        /// <summary>
        /// Gets sample count
        /// </summary>
        public uint SampleCount { get; private set; }

        /// <summary>
        /// Gets sample mean
        /// </summary>
        public double Mean { get; private set; }

        /// <summary>
        /// Gets sample total
        /// </summary>
        public double Total { get; private set; }

        /// <summary>
        /// Gets last added value
        /// </summary>
        public double Last { get; private set; }

        /// <summary>
        /// Gets max value of all added samples
        /// </summary>
        public double Max { get; private set; }

        /// <summary>
        /// Gets min value of all added samples
        /// </summary>
        public double Min { get; private set; }

        /// <summary>
        /// Gets sample variance
        /// </summary>
        public double Variance 
        {
            get 
            {
                if (this.SampleCount > 1)
                {
                    return this.preVariance / (this.SampleCount - 1);
                }
                else
                {
                    return 0;
                }
            } 
        }

        /// <summary>
        /// Gets sample standard deviation
        /// </summary>
        public double StdDev
        {
            get
            {
                if (this.SampleCount > 1)
                {
                    return Math.Sqrt(this.Variance);
                }
                else
                {
                    return 0;
                }
            }
        }

        /// <summary>
        /// Reset sampler to clean state
        /// </summary>
        public void Reset()
        {
            // We could choose to have nullable Min and Max, but that would imply an additional check
            // on every Add() and harder semantics for the caller.
            this.preVariance = 0;
            this.Mean = 0;
            this.SampleCount = 0;
            this.Max = double.MinValue;
            this.Min = double.MaxValue;
            this.Total = 0;
            this.Last = 0;
        }
       
        /// <summary>
        /// Helper method to add an array of samples in a single operation
        /// </summary>
        /// <param name="values">Array of samples to be added to sampler</param>
        public void Add(IEnumerable<double> values)
        {
            foreach (double value in values)
            {
                this.Add(value);
            }
        }

        /// <summary>
        /// Computes rolling variance (Knuth method Volume 2, page 232, 3rd edition of TAOCP)
        /// </summary>
        /// <param name="value">New measured value to be added to the sampler</param>
        public void Add(double value)
        {
            if (this.Max < value) 
            {
                this.Max = value;
            }

            if (this.Min > value)
            {
                this.Min = value;
            }

            ++this.SampleCount;

            this.Total += value;

            this.Last = value;

            double delta = value - this.Mean;

            this.Mean += delta / this.SampleCount;

            this.preVariance += delta * (value - this.Mean);
        }
    }
}
